package se.webbzon.oschi01.worldarea;

import java.util.Collection;

import javax.media.j3d.BranchGroup;
import javax.media.j3d.Group;

import se.webbzon.oschi01.j3d.AutoOrderedGroup;
import se.webbzon.oschi01.worldcamera.WorldCameraPane;
import se.webbzon.oschi01.worldobject.WorldObject;
import se.webbzon.oschi01.worldobject.WorldStaticObject;
import se.webbzon.oschi01.worldobject.appearance.Appearance3D;

public class WorldAppearance3D {
        
        // The properties that can be set for the root appearance 
        public final static int RENDERING_ORDER = (1<<0);
        
        // The properties of this root appearance
        private final int properties;
        
        // The physical universe of the world area (the root node)
        private final BranchGroup physicalUniverse;
        
        // The group in which instances are added to
        private final Group instanceGroup;
        
        // A world area listener
        private final WorldAreaListenerImpl listener;
        
        // The world attached to this world appearance
        private WorldArea world;
        
        /*============================================================
        Constructors
        ============================================================*/
        
        public WorldAppearance3D(int worldAreaViewProperties) {
        	this(worldAreaViewProperties,null);
        }
        
        /** Creates a new root appearance. This root is the visual representation
         * of the content in the world area. This class is strictly bounded to the
         * Java3D environment and is therefore protected by the friendly annotation. **/
        public WorldAppearance3D(int worldAreaViewProperties, WorldArea world) {
                properties = worldAreaViewProperties;
                this.world = world;
                physicalUniverse = new BranchGroup();
                physicalUniverse.setCapability(BranchGroup.ALLOW_DETACH);
                if (isSet(properties,RENDERING_ORDER)) {
                        instanceGroup = new AutoOrderedGroup();
                        physicalUniverse.addChild(instanceGroup);
                } else
                        instanceGroup = physicalUniverse;
                
                instanceGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND);
                instanceGroup.setCapability(Group.ALLOW_CHILDREN_READ);
                instanceGroup.setCapability(Group.ALLOW_CHILDREN_WRITE);
                physicalUniverse.compile();
                
                listener = new WorldAreaListenerImpl();
                setWorld(world);
        }
        
        /*============================================================
        Public Methods
        ============================================================*/
        
        /** Sets the world attached to this world appearance. **/
        public void setWorld(WorldArea world) {
        	if (this.world != world) {
        		if (this.world != null) {
        			this.world.removeListener(listener);
        			clearInstances();
        		}
        		if (world != null) {
        			addAllInstances(world);
        			world.addListener(listener);
        		}
        		this.world = world;
        	}
        }
        
        /** Returns the world attached to this world appearance. **/
        public WorldArea getWorld() {
        	return world;
        }
        
        /** Performs the onPreDraw event for all instances in the
         * attached world area. **/
        public void performPreDraw(WorldCameraPane background) {
        	if (world != null)
        		world.performPreDraw(background);
        }
        
        /** Performs the onDraw event for all instances in the
         * attached world area. **/
        public void performDraw() {
        	if (world != null)
        		world.performDraw();
        }
        
        /** Performs the onPreDraw event for all instances in the
         * attached world area. **/
        public void performPostDraw(WorldCameraPane hud) {
        	if (world != null)
        		world.performPostDraw(hud);
        }
        
        /** Returns the Java3D branch group containing the visual representation
         *  of the world area associated with this root. **/
        public BranchGroup getJ3dBranch() {
        	return physicalUniverse;
        }
        
        /*============================================================
        Private Methods
        ============================================================*/
        
        /** Returns true if the given property is set in the given properties. **/
        private static final boolean isSet(int properties, int property) {
                return ((properties & property) == property);
        }
        
        /** Adds all instances in the wrapped world area into this world appearance. **/
        private final void addAllInstances(WorldArea world) {
                Collection<WorldStaticObject> instances = world.findInstances(WorldStaticObject.class);
                for (WorldStaticObject instance : instances) {
                        addInstance(instance);
                }
        }
        
        /** Adds a static world object to this world appearance. **/
        private final void addInstance(WorldStaticObject instance) {
                final Appearance3D appearance = instance.getAppearance();
                final BranchGroup branchGroup = appearance.getJ3dBranch();
                
                // Check if the instance has an appearance
                if (branchGroup == null)
                        return;
                
                // Compile branch if it not compiled
                if (!branchGroup.isCompiled())
                        branchGroup.compile();
                
                // Add the branch group to the physical universe
                if (isSet(properties,RENDERING_ORDER))
                        ((AutoOrderedGroup) instanceGroup).addChild(
                                        branchGroup,appearance.getRenderingOrder());
                else
                        instanceGroup.addChild(branchGroup);
        }
        
        /** Removes a static world object from this world appearance. **/
        private final void removeInstance(WorldStaticObject instance) {
        	final BranchGroup branchGroup = instance.getAppearance().getJ3dBranch();

        	// Check if the instance has an appearance
        	if (branchGroup == null)
        		return;

        	// Remove the branch group from the physical universe
        	instanceGroup.removeChild(branchGroup);
        }
        
        /** Clears all the world objects from this world appearance. **/
        private final void clearInstances() {
        	instanceGroup.removeAllChildren();
        }
        
        /*============================================================
        Private Classes
        ============================================================*/
        
        /** An implementation of a WorldAreaListener. **/
        private class WorldAreaListenerImpl implements WorldAreaListener {

                /*============================================================
                Public Methods
                ============================================================*/
                
                @Override public void onInstanceAdd(WorldArea source, WorldObject instance) {
                	if (instance instanceof WorldStaticObject)
                                addInstance((WorldStaticObject) instance);
                }

                @Override public void onInstanceRemove(WorldArea source, WorldObject instance) {
                        if (instance instanceof WorldStaticObject)
                                removeInstance((WorldStaticObject) instance);
                }
                
                @Override public void onInstancesClear(WorldArea source) {
                        clearInstances();
                }

                @Override public void onGoto(WorldArea source, WorldArea destination) {};
                
        }

}